﻿using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;

using GoblinXNA;
using GoblinXNA.Graphics;
using GoblinXNA.SceneGraph;
using Model = GoblinXNA.Graphics.Model;
using GoblinXNA.Device.Generic;
using GoblinXNA.Graphics.Geometry;
using GoblinXNA.UI.UI2D;
using GoblinXNA.Device.Capture;
using GoblinXNA.Device.Vision;
using GoblinXNA.Device.Vision.Marker;
using GoblinXNA.Device.Util;
using GoblinXNA.Physics;
using GoblinXNA.Physics.Newton1;
using GoblinXNA.Helpers;
using GoblinXNA.Shaders;

namespace SplashOn
{

    public class SplashOnMain : Microsoft.Xna.Framework.Game
    {
        GraphicsDeviceManager graphics;      
        Scene scene;

        SpriteFont textFont;

        Boolean useStaticImage = false;

        // Size of the canvas
        float virtualCanvasXdim = 300, virtualCanvasYdim = 300, virtualCanvasZdim = 1;
        // number of independent overlay grids in the x and y axis of the canvas.
        int virtualCanvasOverlaysXitems = 1, virtualCanvasOverlaysYitems = 1;
        // transparency of the canvas.
        float canvasTransparency = 0.2f;

        int shooterID = 0;
        Material shooterMat;
        PrimitiveModel boxModel;
        PrimitiveModel sphereModel;
        PrimitiveModel pyramidModel;

        MarkerNode groundMarker;

        public SplashOnMain()
        {
            graphics = new GraphicsDeviceManager(this);
            graphics.PreferredBackBufferWidth = 1024;
            graphics.PreferredBackBufferHeight = 700;
            Content.RootDirectory = "Content";            

#if WINDOWS_PHONE
            graphics.IsFullScreen = true;
#endif
        }
        
        protected override void Initialize()
        {
            base.Initialize();

#if WINDOWS
            // Display the mouse cursor
            this.IsMouseVisible = true;
#endif

            // Initialize the GoblinXNA framework
            State.InitGoblin(graphics, Content, "");

            // Initialize the scene graph
            scene = new Scene();
            scene.PhysicsEngine = new NewtonPhysics();            
            scene.BackgroundColor = Color.CornflowerBlue;
            scene.PhysicsEngine.Gravity = 400;
            scene.PhysicsEngine.GravityDirection = -Vector3.UnitZ;

            SetupMarkerTracking();

            // Set up the lights used in the scene
            CreateLights();            

            // Create 3D objects
            CreateObjects();

#if WINDOWS
            // Add a keyboard press handler for user input
            KeyboardInput.Instance.KeyPressEvent += new HandleKeyPress(KeyPressHandler);
            MouseInput.Instance.MouseClickEvent += new HandleMouseClick(MouseClickHandler);
#elif WINDOWS_PHONE
            MouseInput.Instance.MousePressEvent += new HandleMousePress(MousePressHandler);
#endif
            State.ShowFPS = true;
            State.ShowTriangleCount = false;
            State.ShowNotifications = false;          
        }

        private void SetupMarkerTracking()
        {
            IVideoCapture captureDevice = null;

            if (useStaticImage)
            {
                captureDevice = new NullCapture();
                captureDevice.InitVideoCapture(0, FrameRate._30Hz, Resolution._1600x1200,
                    ImageFormat.R8G8B8_24, false);
                ((NullCapture)captureDevice).StaticImageFile = "MarkerGround.gif";
            }
            else
            {
                // Create our video capture device that uses DirectShow library. Note that 
                // the combinations of resolution and frame rate that are allowed depend on 
                // the particular video capture device. Thus, setting incorrect resolution 
                // and frame rate values may cause exceptions or simply be ignored, depending 
                // on the device driver.  The values set here will work for a Microsoft VX 6000, 
                // and many other webcams.
                captureDevice = new DirectShowCapture2();
                captureDevice.InitVideoCapture(0, FrameRate._60Hz, Resolution._1600x1200,
                    ImageFormat.R8G8B8_24, false);
            }

            // Add this video capture device to the scene so that it can be used for
            // the marker tracker
            scene.AddVideoCaptureDevice(captureDevice);

            ALVARMarkerTracker tracker = new ALVARMarkerTracker();
            tracker.MaxMarkerError = 0.02f;
            tracker.InitTracker(captureDevice.Width, captureDevice.Height, "calib.xml", 32.4f);

            // Set the marker tracker to use for our scene
            scene.MarkerTracker = tracker;

            // Display the camera image in the background. Note that this parameter should
            // be set after adding at least one video capture device to the Scene class.
            scene.ShowCameraImage = true;
        }

        private void CreateLights()
        {
            // Create two directional light sources
            LightSource lightSource1 = new LightSource();
            lightSource1.Direction = new Vector3(-1, -1, -1);
            lightSource1.Diffuse = Color.White.ToVector4();
            lightSource1.Specular = new Vector4(0.6f, 0.6f, 0.6f, 1);

            LightSource lightSource2 = new LightSource();
            lightSource2.Direction = new Vector3(0, 1, 0);
            lightSource2.Diffuse = Color.White.ToVector4() * .2f;
            lightSource2.Specular = new Vector4(0.2f, 0.2f, 0.2f, 1);

            // Create a light node to hold the light sources
            LightNode lightNode1 = new LightNode();
            lightNode1.LightSource = lightSource1;

            LightNode lightNode2 = new LightNode();
            lightNode2.LightSource = lightSource2;

            // Add this light node to the root node
            scene.RootNode.AddChild(lightNode1);
            scene.RootNode.AddChild(lightNode2);
        }

        private void CreateObjects()
        {
            boxModel = new Box(10 * Vector3.One);
            sphereModel = new Sphere(5f, 20, 20);
            pyramidModel = new Cylinder(5f, 5f, 20, 20);
            // Loads a textured model of a ship
            ModelLoader loader = new ModelLoader();
            groundMarker = new MarkerNode(scene.MarkerTracker, "SplashOnGround.xml");

            /* for creating the virtual canvas and the transparent RED (semi - transparent) overlay */
            // Since material is common it can be outside the other code. // TODO - Check for problems with referenceing LATER.
            Material virtualCanvasOverlayNodeMaterial = new Material();
            Color c = new Color(Color.Red.ToVector4().X, Color.Red.ToVector4().Y, Color.Red.ToVector4().Z, canvasTransparency);
            //Color c = new Color(Color.Red.ToVector4().X, Color.Red.ToVector4().Y + (i * 0.0006f), Color.Red.ToVector4().Z + (j * 0.0006f), 0.3f);
            //Console.WriteLine(c.ToVector4().ToString());
            virtualCanvasOverlayNodeMaterial.Diffuse = c.ToVector4();
            virtualCanvasOverlayNodeMaterial.Ambient = c.ToVector4();
            virtualCanvasOverlayNodeMaterial.Specular = c.ToVector4();
            virtualCanvasOverlayNodeMaterial.SpecularPower = 10;

            // Creating the Canvas Node.
            GeometryNode virtualCanvasNode = new GeometryNode("virtualCanvasNode");
            virtualCanvasNode.Model = new Box(virtualCanvasXdim, virtualCanvasYdim, virtualCanvasZdim);
            virtualCanvasNode.AddToPhysicsEngine = true;
            virtualCanvasNode.Physics.Interactable = true;
            virtualCanvasNode.Physics.Collidable = true;
            virtualCanvasNode.Physics.Shape = ShapeType.Box;
            //virtualCanvasNode.Physics.Mass = 50;
            Material virtualCanvasNodeMaterial = new Material();
            virtualCanvasNodeMaterial.Diffuse = Color.Gray.ToVector4();
            virtualCanvasNodeMaterial.Ambient = Color.Gray.ToVector4();
            virtualCanvasNodeMaterial.Specular = Color.Gray.ToVector4();
            virtualCanvasNodeMaterial.SpecularPower = 10;
            virtualCanvasNode.Material = virtualCanvasNodeMaterial;
            TransformNode virtualCanvasNodeTransNode = new TransformNode("virtualCanvasNodeTransNode");
            virtualCanvasNodeTransNode.AddChild(virtualCanvasNode);
            virtualCanvasNodeTransNode.Translation = new Vector3(0, 0, 0);

            // Creating the Overlay Node ....
            GeometryNode[,] virtualCanvasOverlayNodes = new GeometryNode[virtualCanvasOverlaysXitems, virtualCanvasOverlaysYitems];
            TransformNode[,] virtualCanvasOverlayTransNodes = new TransformNode[virtualCanvasOverlaysXitems, virtualCanvasOverlaysYitems];
            float virtualCanvasOverlayNodesXdim = virtualCanvasXdim / virtualCanvasOverlaysXitems;
            float virtualCanvasOverlayNodesYdim = virtualCanvasYdim / virtualCanvasOverlaysYitems;
            for(int i=0; i<virtualCanvasOverlaysXitems; ++i) {
                for(int j=0; j<virtualCanvasOverlaysYitems; ++j) {
                    virtualCanvasOverlayNodes[i, j] = new GeometryNode();
                    virtualCanvasOverlayNodes[i, j].Model = new Box(virtualCanvasOverlayNodesXdim, virtualCanvasOverlayNodesYdim, 1);
                    virtualCanvasOverlayNodes[i, j].AddToPhysicsEngine = true;
                    //virtualCanvasOverlayNodes[i, j].Physics.Mass = 0;
                    virtualCanvasOverlayNodes[i, j].Physics.Interactable = true;
                    virtualCanvasOverlayNodes[i, j].Physics.Collidable = true;
                    virtualCanvasOverlayNodes[i, j].Physics.Shape = ShapeType.Box;
                    virtualCanvasOverlayNodes[i, j].Material = virtualCanvasOverlayNodeMaterial;
                    virtualCanvasOverlayTransNodes[i, j] = new TransformNode();
                    virtualCanvasOverlayTransNodes[i, j].AddChild(virtualCanvasOverlayNodes[i, j]);
                    float x = virtualCanvasXdim / 2 * -1 + virtualCanvasXdim / (virtualCanvasOverlaysXitems * 2);
                    float y = virtualCanvasYdim / 2 * -1 + virtualCanvasYdim / (virtualCanvasOverlaysYitems * 2); ;
                    //Console.WriteLine("x: " + x + ", y: " + y);
                    virtualCanvasOverlayTransNodes[i, j].Translation = new Vector3(x + i * virtualCanvasOverlayNodesXdim, y + j * virtualCanvasOverlayNodesYdim, 1.001f);
                    Console.WriteLine("Adding Overlay Item - " + i + ", " + j + " :: " + virtualCanvasOverlayTransNodes[i, j].Translation.ToString());
                //    groundMarker.AddChild(virtualCanvasOverlayTransNodes[i, j]);
                }
            }
            /* end of creating virtual canvas */

            shooterMat = new Material();
            shooterMat.Diffuse = Color.Purple.ToVector4();
            shooterMat.Specular = Color.Green.ToVector4();
            shooterMat.SpecularPower = 10;

            groundMarker.AddChild(virtualCanvasNodeTransNode);
            scene.RootNode.AddChild(groundMarker);
        }

        private void MouseClickHandler(int button, Point mouseLocation)
        {
            // Shoot a box if left mouse button is clicked
            if (button == MouseInput.LeftButton)
            {
                Vector3 nearSource = new Vector3(mouseLocation.X, mouseLocation.Y, 0);
                Vector3 farSource = new Vector3(mouseLocation.X + 200, mouseLocation.Y + 200, 1);

                Matrix viewMatrix = State.ViewMatrix; 
                // The secret sauce that enables you to draw something that goes from your phone to the ground marker. 

                Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource,
                    State.ProjectionMatrix, viewMatrix, Matrix.Identity);
                Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource,
                    State.ProjectionMatrix, viewMatrix, Matrix.Identity);


                //Vector3 nearPoint = graphics.GraphicsDevice.Viewport.Unproject(nearSource,
                //    State.ProjectionMatrix, State.ViewMatrix, Matrix.Identity);
                //Vector3 farPoint = graphics.GraphicsDevice.Viewport.Unproject(farSource,
                //    State.ProjectionMatrix, State.ViewMatrix, Matrix.Identity);

                Console.WriteLine("near = " + nearPoint.X + ", " + nearPoint.Y + ", " + nearPoint.Z);
                Console.WriteLine("farPoint = " + farPoint.X + ", " + farPoint.Y + ", " + farPoint.Z);

                ShootBox(nearPoint, farPoint);
            }
        }

        private void ShootBox(Vector3 near, Vector3 far)
        {
            GeometryNode shootBox = new GeometryNode("ShooterBox" + shooterID++);

            shooterMat = new Material();
            shooterMat.Diffuse = Color.Purple.ToVector4();
            shooterMat.Specular = Color.Green.ToVector4();
            shooterMat.SpecularPower = 10;

            Random rand = new Random();
            int j = rand.Next(3);
            if (j == 1)
            {
                shootBox.Model = boxModel;
                shooterMat.Diffuse = Color.Cyan.ToVector4();
            }
            else if (j == 2)
            {
                shootBox.Model = sphereModel;
                shooterMat.Diffuse = Color.Purple.ToVector4();
            }
            else
            {
                shootBox.Model = pyramidModel;
                shooterMat.Diffuse = Color.Orchid.ToVector4();
            }
           

            shootBox.Material = shooterMat;
            shootBox.Physics.Interactable = true;
            shootBox.Physics.Collidable = true;
            shootBox.Physics.Shape = GoblinXNA.Physics.ShapeType.Box;
            shootBox.Physics.Mass = 0.01f;
            shootBox.AddToPhysicsEngine = true;
          //  shootBox.Physics.ApplyGravity = false;
            // Calculate the direction to shoot the box based on the near and far point
            Vector3 linVel = far - near;
            linVel.Normalize();
            // Multiply the direction with the velocity of 20
            linVel *= 900f;

            // Assign the initial velocity to this shooting box
            shootBox.Physics.InitialLinearVelocity = linVel;

            TransformNode shooterTrans = new TransformNode();
            shooterTrans.Translation = near;

            groundMarker.AddChild(shooterTrans);
            shooterTrans.AddChild(shootBox);
        }


        private void KeyPressHandler(Keys keys, KeyModifier modifier)
        {
           
        }

        protected override void LoadContent()
        {
            textFont = Content.Load<SpriteFont>("Sample");
        }

        protected override void UnloadContent()
        {
            Content.Unload();
        }

        protected override void Update(GameTime gameTime)
        {           
            scene.Update(gameTime.ElapsedGameTime, gameTime.IsRunningSlowly, this.IsActive);
        }
        
        protected override void Draw(GameTime gameTime)
        {
            // Draw a 2D text string at the center of the screen
            UI2DRenderer.WriteText(Vector2.Zero, "Initialized!!!!", Color.GreenYellow,
                textFont);           

            scene.Draw(gameTime.ElapsedGameTime, gameTime.IsRunningSlowly);
        }
    }
}
